using System;
using NUnit.Framework;
using System.IO;

namespace HappyTemplate.Tests
{
	[TestFixture]
	public class ExecutionErrorTests : TestFixtureHelper
	{
		readonly StringWriter _sw = new StringWriter();

		void CauseExectionException(string template, TemplateExecutionErrorCode teec)
		{
			CauseExectionException(ParseTemplate(template), teec);
		}

		void CauseExectionException(Template template, TemplateExecutionErrorCode teec)
		{
			try
			{	
				_sw.GetStringBuilder().Length = 0;
				template.Execute(_context, _sw);
				Assert.Fail("Template did not throw TemplateExecutionException");
			}
			catch (TemplateExecutionException ex)
			{
				Assert.AreEqual(teec, ex.Key, "Message was:  " + ex.Message);
			}
		}

		
		[Test]
		public void IdentifierNotFound_Identifier()
		{
			CauseExectionException("<$unknown$>", TemplateExecutionErrorCode.IdentifierNotFound_Identifier	);
		}	

		[Test]
		public void TemplateDoesNotExist_Identifier()
		{
			CauseExectionException("<$misspelled()$>", TemplateExecutionErrorCode.ObjectDoesNotExist_Identifier);
		}

		[Test]
		public void DoesNotImplementIEnumerable_Identifier()
		{
			Template t = ParseTemplate("<$foreach(n in nonEnumerable) n next$>");
			_context["nonEnumerable"] = 100;
			CauseExectionException(t, TemplateExecutionErrorCode.DoesNotImplementIEnumerableInForEach);
		}

		private Template ParseArgCountTemplate(string template)
		{
			string happyt = "callMe(a, b, c)<{<$ a b c $>}> testTemplate() <{" + template + "}>";
			return ParseTemplateSet(happyt).GetTemplate("testTemplate");
		}

		[Test]
		public void	IncorrectNumberOfArguments_TemplateName_Expected_Actual_OneTooFiew()
		{
			//One too few
			CauseExectionException(ParseArgCountTemplate("<$callMe(\"1\", \"2\")$>"), 
				TemplateExecutionErrorCode.IncorrectNumberOfArguments_ObjectName_Expected_Actual);
		}	

		[Test]
		public void IncorrectNumberOfArguments_TemplateName_Expected_Actual_OneTooMany()
		{
			//One too many
			CauseExectionException(ParseArgCountTemplate("<$callMe(\"1\", \"2\", \"3\", \"4\")$>"),
				TemplateExecutionErrorCode.IncorrectNumberOfArguments_ObjectName_Expected_Actual);
		}

		[Test]
		public void IncorrectNumberOfArguments_TemplateName_Expected_Actual_None()
		{
			//None
			CauseExectionException(ParseArgCountTemplate("<$callMe()$>"),
				TemplateExecutionErrorCode.IncorrectNumberOfArguments_ObjectName_Expected_Actual);
		}

		[Test]
		public void IncorrectNumberOfArguments_TemplateName_Expected_Actual_Correct()
		{
			//Correct (should not cause exception)
			WriteTemplate(ParseArgCountTemplate("<$callMe(\"1\", \"2\", \"3\")$>"), _context);
		}

		[Test]
		public void IncorrectNumberOfArguments_ObjectName_Expected_Actual_ForBuiltins()
		{
			CauseExectionException(ParseTemplate("<$split(\"only one argument instead of two\")$>"),
				TemplateExecutionErrorCode.IncorrectNumberOfArguments_ObjectName_Expected_Actual);
		}

		[Test]
		public void IncorrectNumberOfArguments_ObjectName_MinExpected_MaxExpected_Actual_ForBuiltins()
		{
			CauseExectionException(ParseTemplate("<$isOneOf(\"only one argument instead of 3-32\")$>"),
				TemplateExecutionErrorCode.IncorrectNumberOfArguments_ObjectName_MinExpected_MaxExpected_Actual);
		}

		[Test]
		public void MultipartIdentifierNotFound_Identifier_PartNotFound_NoError()
		{
			Template t = ParseTemplate("<$someString.Length$>");
			_context["someString"] = "anonymous";
			WriteTemplate(t, _context);
		}

		[Test]
		public void MultipartIdentifierNotFound_Identifier_PartNotFound()
		{
			Template t = ParseTemplate("<$someString.Length.PartNotFound$>");
			_context["someString"] = "anonymous";
			CauseExectionException(t, TemplateExecutionErrorCode.MultipartIdentifierNotFound_Identifier_PartNotFound);
		}

		[Test]
		public void	InputValueDoesNotExistInLookup_LookupName_InputValue()
		{
			TemplateSet t = ParseTemplateSet(@"
lookup someLookup
{
	""a"" = ""a"",
	""b"" = ""b""
}
testTemplate() <{<$someLookup[""invalid""]$>}>	
");
			CauseExectionException(t.GetTemplate("testTemplate"), 
				TemplateExecutionErrorCode.InputValueDoesNotExistInLookup_LookupName_InputValue);
		}

		private class Dummy
		{
// ReSharper disable UnusedMemberInPrivateClass
			public string SomeProperty { get { throw new ApplicationException("I'm intentional"); }}
// ReSharper restore UnusedMemberInPrivateClass
		}

		[Test]
		public void ExceptionWhileRetrievingPropertyValue_Identifier_PartInError_ExceptionClass_Message_StackTrace()
		{
			Template t = ParseTemplate("<$dummy.SomeProperty$>");
			_context["dummy"] = new Dummy();
            CauseExectionException(t, 
				TemplateExecutionErrorCode.ExceptionWhileRetrievingPropertyValue_Identifier_PartInError_ExceptionClass_Message_StackTrace);
		}

		[Test]
		public void TemplateNamesCannotBeMutlipart()
		{
			CauseExectionException(ParseTemplate("<$some.multipart.template.name(anArgument)$>"),
				TemplateExecutionErrorCode.TemplateNamesCannotBeMutlipart);
		}	

		[Test]
		public void	MaximumStackSizeExceeded()
		{
			TemplateSet ts = ParseTemplateSet(@"recursiveTemplate(arg) <{<$recursiveTemplate(arg)$>}>");
			ts.Context["arg"] = "someUnimportantValue";
			CauseExectionException(ts.GetTemplate("recursiveTemplate"),
				TemplateExecutionErrorCode.MaximumStackSizeExceeded);
		}

		[Test]
		public void InvalidPropertyOfNonTemplateOjbect()
		{
			Template t = base.ParseTemplate("<$nonTemplateObject.InvalidProperty$>");

			_context["nonTemplateObject"] = "I'm not a template object.";

			try
			{
				WriteTemplate(t, _context);
				Assert.Fail("TemplateExecutionException was not thrown");
			}
			catch(TemplateExecutionException ex)
			{
				Assert.AreEqual(TemplateExecutionErrorCode.MultipartIdentifierNotFound_Identifier_PartNotFound, ex.Key);
			}
		}

		[Test]
		public void NullArgumentToBuiltInFunction()
		{
			Template t = base.ParseTemplate("<$toLower(null)$>");

			try
			{
				WriteTemplate(t, _context);
				Assert.Fail("TemplateExecutionException was not thrown");
			}
			catch (TemplateExecutionException ex)
			{
				Assert.AreEqual(TemplateExecutionErrorCode.ArgumentNull_ArgumentNumber_FunctionName	, ex.Key);
			}
		}
	}
}
	